<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script>
    /*
    * 任何数组方法，都可以最后接收额外的参数，就是this的指向对象，即回调函数调用的时候的this
    *
    * */

    /*
    * 数组方法的封装 reduce map filter every some forEach ...
    *
    * map filter
    *
    * */

    //myMap封装
    Array.prototype.myMap = function (cb){ //map方法接收一个参数回调函数

        //map方法最终会返回一个新数组 所以先创建一个结果数组
        let result = []

        //因为被返回的这个新数组（也就是结果数组）中的每一项都来自于回调函数的返回结果 所以要对回调函数的每一项进行循环遍历
        for(let i = 0;i < this.length;i++){ //这里的this一定是指向调用时被实例出来的那个数组对象
            //循环内部要对回调函数去执行 并且对回调函数的每一项进行操作 并将每次循环后回调函数执行的每一项的返回结果赋值给结果数组
            result[i] = cb(this[i],i,this) //回调函数接收三个参数：数组每一项元素，每一项元素的下标，数组本身
        }

        //最终将循环完成后的结果数组返回出去
        return result
    }

    //myFilter
    Array.prototype.myFilter = function (cb){ //接收一个参数回调函数

        //filter方法最终会返回一个新数组 所以先创建一个结果数组
        let result = []

        //结果数组result中只接收判断条件为真的每一项
        for(let i = 0;i < this.length;i++){ //这里的this一定是指向调用时被实例出来的那个数组对象
            if(cb(this[i],i,this)){ //回调函数可接收三个参数：数组中每一项元素，每一项元素的下标，数组本身
                result.push(this[i]) //只有判断条件为真的数组项才会被添加到结果数组中
            }
        }

        //循环结束之后将结果数组返回
        return result
    }


    //验证一下
    let arr = [1,2,3,4]

    //原本自带的方法
    console.log(arr.map(i => i + 1))

    //我们封装的方法
    console.log(arr.myMap(i => i + 1))

    //看看有没有改变原数组，理论上是不改变原数组的
    console.log(arr)

    console.log(arr.filter(i => i % 2 === 0))

    console.log(arr.myFilter(i => i % 2 === 0))

    console.log(arr)


    /*
    * 强行自己手动给字符串添加forEach方法，在String构造函数的原型prototype上添加
    * */
    String.prototype.forEach = function (cb){ //接收一个参数回调函数

        //forEach方法是遍历数组中的每一项
        for(let i = 0;i < this.length;i++){
            cb(this[i],i,this) //回调函数接收三个参数：数组的每一项元素，每一项元素的下标，数组本身
        }
    }
    //现在任何一个字符串都从原型上拥有了forEach方法
    let str = "asdfg"
    str.forEach(i => console.log(i)) //打印字符串中每一个字母元素

    //如果是以前我们是这样写的
    arr.forEach.call(str,i => console.log(i))

    //字符串实现reverse
    String.prototype.reverse = function (){
        return this.split("").reverse().join("")
    }//split将字符串 以空字符串的形式进行分割 转换成数组，reverse对数组内容进行反转，join将数组转换成字符串
    console.log(str.reverse()) //字符串就被翻转了


    /*
    * js的设计理念：鸭子模型
    *   看起来像，用起来也像的东西，那么就可以是通用的。
    *   例如字符串和数组，数组和类数组，所以它们的方法可以通过call等方法进行绑定而得以通用
    *
    * 什么时候才在原型上进行开发：如果只是少数几个字符串需要借用数组方法，就没必要，如果是大多数字符串都需要借用数组方法，那就对原型进行添加
    *
    * */


    /*
    * 借用 call 方法实现 bind 方法绑定this指向
    *
    *   bind方法
    *   1.是函数的方法，需要写到Function.prototype中
    *   2.返回一个函数 这个函数内部this主体一定是 bind方法执行的时候传入的第一个参数
    *     bind函数执行之后的返回值是一个函数，这个返回的函数也有返回值，
    *     bind函数执行之后返回的这个函数的执行结果（返回值），就是调用bind 的原函数执行之后返回结果（返回值）
    *   (为什么bind函数的返回函数的返回值要与调用bind方法原函数的返回值保持一致，因为bind方法仅仅只是修改原函数的this指向，并没有修改原函数的执行过程与返回值)
    *   (并且由于bind函数返回值的存在，我们在使用bind方法之后，再去使用call方法却无法修改bind绑定之后的this指向，就是因为call修改的，
    *   是bind方法的返回值的this指向，而bind方法的返回值是一个函数，函数还有返回值，在返回值函数的返回值中bind才真正修改绑定了this指向)
    *   3.接收一个及以上的参数，可以分批次传参
    *
    * */

    //回顾一下bind方法的功能
    function foo(a,b,c){
        console.log(a,b,c)
        console.log(this)
        return 1
    }
    let o = {name:"o"}
    let f = foo.bind(o,1,2) //bind修改函数的调用主体的this指向，this指向绑定到传入的第一个参数o对象身上，并且bind具有分批次传参的功能，
    //函数foo可接收三个参数，这里第一次执行只传入了参数a = 1，b = 2，最前面那个参数o不是函数foo的参数，o是bind需要绑定this的参数对象
    f(3) //第二次f执行将c = 3传入

    //明白了bind的功能后，开始进行原型上的封装
    Function.prototype.myBind = function (obj,...args1){ //接收的第一个参数，对象，这个对象就是bind需要绑定this指向的调用主体，
        //接收的第二个参数，是第一组分批次传参的剩余参数arguments1，因为这个是数组，所以需要使用...解构赋值的方法将它们拆开

        //bind方法不是一开始便修改了this指向，一开始的this是指向调用myBind的主体，我们可以用self来表示一开始的this指向
        let self = this

        //bind方法的执行结果是返回一个函数 在返回的函数中才去执行修改最初的this指向 绑定到传入的参数对象身上
        //bind方法的返回值，是一个函数
        return function (...args2){ //这里接收分批次传参第二次传入的第二组剩余参数arguments2
            //在返回的函数内部去执行修改原本self的this指向 这里self用到了闭包的知识
            //这个bind返回的函数也有返回值return
            return self.call(obj,...agrs1,...args2) //将self所代表的一开始this指向绑定到传入的obj对象身上
            //这最后一个return就是bind函数执行之后 返回的函数的返回值 这个返回值的内容和调用bind方法的原函数的返回值是一致的
        }
    }

    /*如果使用箭头函数来封装
    Function.prototype.myBind = function (obj,...args1){
        return (...args2) => {this.call(obj,...args1,...args2)}
    }*/

    console.log(foo.myBind(o, 1, 2)(3)) //函数foo调用myBind方法，第一次传入参数1,2,第二次传入参数3


</script>
</body>
</html>